
/* 
  请描述Vue2中响应式数据原理
    - Vue响应式指的是:组件的data数据发生改变,立刻就会触发视图的更新
    - 响应式使用的就是:发布-订阅模式(也称作观察者模式)主要是:数据劫持 + 依赖收集
    - 数据劫持的原理:
      Vue2通过Object.defineProperty来进行数据劫持,把data中每一个对象的属性使用defineProperty重写getter和setter,具体做法书写一个Observer类,其实就是发布订阅模式中的发布器,主要功能就是对数据进行劫持,并在getter和setter中完成功能,在getter中收集依赖,在setter中通知依赖进行视图更新
    - 依赖收集原理:
      - 有一个Dep类,其实就是发布订阅模型的订阅器,Dep上有收集依赖和通知依赖更新的方法,每一个数据在劫持的时候,创建一个dep实例,在getter中调用dep实例的收集依赖方法,在setter中调用dep实例的通知依赖更新方法
      - 有一个Watcher类,Wacther身上有获取数据的方法,和重新获取数据并更新视图的update方法.每次模板中编译data数据的时候,都会创建一个对应的watcher实例,wacther实例会在第一个获取时候的时候被收集到当前数据的dep中,在数据更新时,会通知所有watcher调用其身上的update方法,达到响应式的目的
*/
let W = null
// 6.定义一个Watcher类 在订阅的时候实例化它
class Watcher {
  // 接受对象和对象的属性
  constructor(obj, key) {
    this.obj = obj
    this.key = key
    // 刚开始就要获取值
    W = this
    this.get()
    W = null
  }
  get() {
    return this.obj[this.key]
  }
  update() {
    console.log('更新了' + this.get());
  }
}
// 5.定义一个dep类 (收集依赖 通知更新 )
class Dep {
  constructor() {
    // 每次实例的时候 创建一个空的容器放watcher
    this.subs = []
  }
  // 实例上有两个函数 一个用来收集watcher一个用来通知所有的watcher重新获取新的数据并渲染页面
  getDep(W) {
    // 收集watcher
    this.subs.push(W)
  }
  updatedDep() {
    // 容器里放着都是watcher 调用watcher里更新的方法
    this.subs.forEach(watcher => { watcher.update() })
  }
}

// 4.封装一个defineReactive 进行响应式操作
function defineReactive(_data, key, value) {
  // 再次深度遍历 
  observe(value)
  // 每次对一个值进行响应式操作的时候实例化一个Dep类
  const dep = Dep()
  Object.defineProperty(_data, key, {
    get() {
      // 正在获取数据
      console.log("正在获取数据");
      if (W) {
        dep.getDep(W)
      }
      return value
    },
    set(newVal) {
      // 正在设置数据 需要dep提示watcher重新获取数据
      console.log("正在设置数据");
      value = newVal
      // 提示更新
      dep.updatedDep()
    }
  })

}


// 3.封装一个Observer类(发布-订阅模型中的发布者)
class Observer {
  constructor(_data) {
    // 把这个_data放到实例身上
    this._data = _data
    //判断数据类型 如果是数组就是执行数组的方法 如果是对象就执行对象的方法
    if (Array.isArray(this._data)) {
      this.ObserverAry(_data)
    } else {
      this.ObserverObj(_data)
    }
  }
  //封装处理数组的方法和处理对象的方法
  // 处理数组的方法
  ObserverAry(_data) {
    // 再次遍历 是为了遍历数组里是不是还有对象 对对象进行深度监听
    _data.forEach(item => observe(item))
  }
  // 对象的方法
  ObserverObj(_data) {
    // 把每一个属性交给defineReactive来处理
    for (let key of Object.keys(_data)) {
      // 把对象 属性和值都传过去
      defineReactive(_data, key, _data[key])
    }
  }
}

// 2.封装一个observe函数
function observe(_data) {
  // 进行判断类型,如果是对象就实例化Observer 并传入值
  if (typeof _data !== "object" || _data === null) return
  new Observer(_data)
}
// 1.封装一个Vue构造函数  接受一个配置对象
function Vue(options) {
  // 将options配置对象内的data,放在实例对象的this的_data属性上
  this._data = options.data
  // 1.1进行数据代理
  for (let key of Object.keys(this._data)) {
    Object.defineProperty(this, key, {
      get() {
        return this._data[key]
      },
      set(newVal) {
        this._data[key] = newVal
      }
    })
  }
  // 进行数据劫持
  observe(this._data)
}
// 创建一个实例化对象
const vm = new Vue({
  data: {
    count: 0,
    student: {
      name: '罗飞'
    },
    Ary: [
      { id: 1, type: '胡辣汤' },
      { id: 2, type: '热干面' },
      { id: 3, type: '馄饨' }
    ]
  }
})